package com.zifeng.utils;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zifeng.common.Message;
import com.zifeng.dao.TransactionDao;
import com.zifeng.pojo.Block;
import com.zifeng.pojo.Transaction;
import com.zifeng.service.impl.BlockServiceImpl;
import com.zifeng.common.BlockConstant;
import org.apache.commons.beanutils.BeanUtils;
import org.java_websocket.WebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * p2p网络服务类
 */
@Service
public class P2PMessageHandler {

    @Autowired
    BlockServiceImpl blockServiceImpl;

    @Autowired
    TransactionDao transactionDao;

    @Autowired
    BlockCache blockCache;

    /**
     * 客户端和服务端共用的消息处理方法
     *
     * @param webSocket
     * @param msg
     * @param sockets
     */
    public void handleMessage(WebSocket webSocket, String msg, List<WebSocket> sockets) {
        try {
            Message message = JSON.parseObject(msg, Message.class);
            System.out.println("接收到IP地址为：" + webSocket.getRemoteSocketAddress().getAddress().toString()
                    + "，端口号为：" + webSocket.getRemoteSocketAddress().getPort() + "的p2p消息："
                    + JSON.toJSONString(message));
            switch (message.getType()) {
                //客户端请求查询最新的区块:1
                case BlockConstant.QUERY_LATEST_BLOCK:
                    write(webSocket, responseLatestBlockMsg());//服务端调用方法返回最新区块:2
                    break;
                //接收到服务端返回的最新区块:2
                case BlockConstant.RESPONSE_LATEST_BLOCK:
                    handleBlockResponse(message.getData(), sockets);
                    break;
                //客户端请求查询整个区块链:3
                case BlockConstant.QUERY_BLOCKCHAIN:
                    write(webSocket, responseBlockChainMsg());//服务端调用方法返回最新区块:4
                    break;
                //直接接收到其他节点发送的整条区块链信息:4
                case BlockConstant.RESPONSE_BLOCKCHAIN:
                    handleBlockChainResponse(message.getData(), sockets);
                    break;
            }
        } catch (Exception e) {
            System.out.println("处理IP地址为：" + webSocket.getRemoteSocketAddress().getAddress().toString()
                    + "，端口号为：" + webSocket.getRemoteSocketAddress().getPort() + "的p2p消息错误:"
                    + e.getMessage());
        }
    }

    /**
     * 处理其它节点发送过来的区块信息
     *
     * @param blockData
     * @param sockets
     */
    public synchronized void handleBlockResponse(String blockData, List<WebSocket> sockets) throws InvocationTargetException, IllegalAccessException {
        //反序列化得到其它节点的最新区块信息
        Block latestBlockReceived = JSON.parseObject(blockData, Block.class);
        //当前节点的最新区块
        Block latestBlock = blockCache.getLatestBlock();

        if (latestBlockReceived != null) {
            if (latestBlock != null) {
                //如果接收到的区块高度比本地区块高度大的多
                if (latestBlockReceived.getBlockIndex() > latestBlock.getBlockIndex() + 1) {
                    broatcast(queryBlockChainMsg());
                    System.out.println("重新查询所有节点上的整条区块链");
                } else if (latestBlockReceived.getBlockIndex() > latestBlock.getBlockIndex() &&
                        latestBlock.getBlockHash().equals(latestBlockReceived.getPreviousHash())) {
                    // TODO 验证先要同步其他节点的传过来的Transaction
//                    List<Transaction> transactions = new ArrayList<>();
//                    for (Transaction transaction : latestBlockReceived.getTransactions()) {
//                        Transaction newTransaction = new Transaction();
//                        // 这里要进行深拷贝，避免插入Transaction表后导致List<Transaction>不一样, 导致计算出来的Hash值跟别的节点传过来的
//                        // block中的Hash值不一样
//                        BeanUtils.copyProperties(newTransaction, transaction);
//                        transactions.add(newTransaction);
//                    }
//                    try {
//                        for (Transaction transaction : transactions) {
//                            QueryWrapper<Transaction> wrapper = new QueryWrapper<>();
//                            wrapper.eq("business_info", transaction.getBusinessInfo());
//                            Transaction sqlTransaction = transactionDao.selectOne(wrapper);
//                            if (sqlTransaction == null) {
//                                transactionDao.insert(transaction);
//                            }
//                        }
//                    } catch (Exception e) {
//                        System.out.println(e);
//                    }
                    for (Transaction transaction : latestBlockReceived.getTransactions()) {
                        transaction.setId(0L);
                    }
                    if (blockServiceImpl.addBlock(latestBlockReceived)) {
                        // 帮助广播最新区块链区块
                        broatcast(responseLatestBlockMsg());
                    }
                    System.out.println("将新接收到的区块加入到本地的区块链");
                }
            } else {
                broatcast(queryBlockChainMsg());
                System.out.println("重新查询所有节点上的整条区块链");
            }
        }
    }

    /**
     * 处理其它节点发送过来的区块链信息
     *
     * @param blockData
     * @param sockets
     */
    public synchronized void handleBlockChainResponse(String blockData, List<WebSocket> sockets) {
        //反序列化得到其它节点的整条区块链信息
        List<Block> receiveBlockchain = JSON.parseArray(blockData, Block.class);
        if (!CollectionUtils.isEmpty(receiveBlockchain) && blockServiceImpl.isValidChain(receiveBlockchain)) {
            //根据区块索引先对区块进行排序
            Collections.sort(receiveBlockchain, new Comparator<Block>() {
                public int compare(Block block1, Block block2) {
                    return (int) (block1.getBlockIndex() - block2.getBlockIndex());
                }
            });

            //其它节点的最新区块
            Block latestBlockReceived = receiveBlockchain.get(receiveBlockchain.size() - 1);
            //当前节点的最新区块
            Block latestBlock = blockCache.getLatestBlock();

            if (latestBlock == null) {
                //替换本地的区块链
                blockServiceImpl.replaceChain(receiveBlockchain);
            } else {
                //其它节点区块链如果比当前节点的长，则处理当前节点的区块链
                if (latestBlockReceived.getBlockIndex() > latestBlock.getBlockIndex()) {
                    if (latestBlock.getBlockHash().equals(latestBlockReceived.getPreviousHash())) {
                        if (blockServiceImpl.addBlock(latestBlockReceived)) {
                            // 帮助广播最新区块
                            broatcast(responseLatestBlockMsg());
                        }
                        System.out.println("将新接收到的区块加入到本地的区块链");
                    } else {
                        // 用长链替换本地的短链
                        blockServiceImpl.replaceChain(receiveBlockchain);
                    }
                }
            }
        }
    }

    /**
     * 全网广播消息
     *
     * @param message
     */
    public void broatcast(String message) {
        List<WebSocket> socketsList = this.getSockets();
        if (CollectionUtils.isEmpty(socketsList)) {
            return;
        }
        System.out.println("======全网广播消息开始：");
        for (WebSocket socket : socketsList) {
            this.write(socket, message);
        }
        System.out.println("======全网广播消息结束");
    }

    /**
     * 向其它节点发送消息
     *
     * @param ws
     * @param message
     */
    public void write(WebSocket ws, String message) {
        System.out.println("发送给IP地址为：" + ws.getRemoteSocketAddress().getAddress().toString()
                + "，端口号为：" + ws.getRemoteSocketAddress().getPort() + " 的p2p消息:" + message);
        ws.send(message);
    }

    /**
     * 查询整条区块链
     *
     * @return
     */
    public String queryBlockChainMsg() {
        return JSON.toJSONString(new Message(BlockConstant.QUERY_BLOCKCHAIN));
    }

    /**
     * 返回整条区块链数据
     *
     * @return
     */
    public String responseBlockChainMsg() {
        Message msg = new Message();
        msg.setType(BlockConstant.RESPONSE_BLOCKCHAIN);
        msg.setData(JSON.toJSONString(blockCache.getBlockChain()));
        return JSON.toJSONString(msg);
    }

    /**
     * 查询最新的区块
     *
     * @return
     */
    public String queryLatestBlockMsg() {
        return JSON.toJSONString(new Message(BlockConstant.QUERY_LATEST_BLOCK));
    }

    /**
     * 返回最新的区块
     *
     * @return
     */
    public String responseLatestBlockMsg() {
        Message msg = new Message();
        msg.setType(BlockConstant.RESPONSE_LATEST_BLOCK);
        Block b = blockCache.getLatestBlock();
        msg.setData(JSON.toJSONString(b));
        return JSON.toJSONString(msg);
    }

    public List<WebSocket> getSockets() {
        return blockCache.getSocketsList();
    }

}
